// ****************************************************************
// Copyright 2012, Stephan Burguchev
// e-mail: me@sburg.net
// ****************************************************************
// *
using System;
using System.Text.RegularExpressions;
using System.Xml;
using System.Xml.Schema;
using System.Xml.Serialization;

namespace Updater.Utils
{
    /// <summary>
    /// </summary>
    [XmlRoot("version")]
    public class Version : IComparable<Version>, IXmlSerializable
    {
        /// <summary>
        ///     Initializes a new instance of the <see cref="Version" /> class.
        /// </summary>
        /// <param name="major">The major version number.</param>
        /// <param name="minor">The minor version number.</param>
        /// <param name="build">The build number.</param>
        /// <param name="revision">The revision number.</param>
        /// <param name="suffix">The suffix.</param>
        public Version(int major, int minor, int build, int revision, string suffix = null)
        {
            this.Major = major;
            this.Minor = minor;
            this.Build = build;
            this.Revision = revision;
            this.Suffix = suffix;
        }

        public Version()
        {
        }

        /// <summary>
        /// </summary>
        public int Build { get; private set; }

        /// <summary>
        /// </summary>
        public int Major { get; private set; }

        /// <summary>
        /// </summary>
        public int Minor { get; private set; }

        /// <summary>
        /// </summary>
        public int Revision { get; private set; }

        /// <summary>
        /// </summary>
        public string Suffix { get; private set; }

        /// <summary>
        /// </summary>
        /// <param name="version">
        /// </param>
        /// <returns>
        /// </returns>
        /// <exception cref="ArgumentNullException">
        /// </exception>
        /// <exception cref="FormatException">
        /// </exception>
        public static Version Parse(string version)
        {
            if (version == null)
            {
                throw new ArgumentNullException("version");
            }

            Match m = Regex.Match(version, @"^\s*(\d+)\.(\d+)\.(\d+)(?:\.(?<rev>\d+))?(?<suf>.*)\s*$");
            if (!m.Success)
            {
                throw new FormatException("Invalid version format.");
            }

            int major = int.Parse(m.Groups[1].Value);
            int minor = int.Parse(m.Groups[2].Value);
            int build = int.Parse(m.Groups[3].Value);
            int revision = m.Groups["rev"].Success
                               ? int.Parse(m.Groups["rev"].Value)
                               : 0;
            string suffix = m.Groups["suf"].Value;

            return new Version(major, minor, build, revision, suffix);
        }

        public static bool TryParse(string version, out Version result)
        {
            if (version == null)
            {
                throw new ArgumentNullException("version");
            }

            try
            {
                result = Parse(version);
                return true;
            }
            catch (FormatException)
            {
                result = null;
                return false;
            }
        }

        /// <summary>
        /// </summary>
        /// <param name="x">
        /// </param>
        /// <param name="y">
        /// </param>
        /// <returns>
        /// </returns>
        public static bool operator ==(Version x, Version y)
        {
            if (ReferenceEquals(x, y))
            {
                return true;
            }
            if (ReferenceEquals(x, null) ||
                ReferenceEquals(y, null))
            {
                return false;
            }

            return x.ToString().ToLower() == y.ToString().ToLower();
        }

        /// <summary>
        /// </summary>
        /// <param name="x">
        /// </param>
        /// <param name="y">
        /// </param>
        /// <returns>
        /// </returns>
        public static bool operator >(Version x, Version y)
        {
            if (ReferenceEquals(x, null))
            {
                return false;
            }
            return x.CompareTo(y) > 0;
        }

        /// <summary>
        /// </summary>
        /// <param name="x">
        /// </param>
        /// <param name="y">
        /// </param>
        /// <returns>
        /// </returns>
        public static bool operator >=(Version x, Version y)
        {
            if (ReferenceEquals(x, null))
            {
                return ReferenceEquals(y, null);
            }
            return x.CompareTo(y) >= 0;
        }

        /// <summary>
        /// </summary>
        /// <param name="x">
        /// </param>
        /// <param name="y">
        /// </param>
        /// <returns>
        /// </returns>
        public static bool operator !=(Version x, Version y)
        {
            return !(x == y);
        }

        /// <summary>
        /// </summary>
        /// <param name="x">
        /// </param>
        /// <param name="y">
        /// </param>
        /// <returns>
        /// </returns>
        public static bool operator <(Version x, Version y)
        {
            return y > x;
        }

        /// <summary>
        /// </summary>
        /// <param name="x">
        /// </param>
        /// <param name="y">
        /// </param>
        /// <returns>
        /// </returns>
        public static bool operator <=(Version x, Version y)
        {
            return y >= x;
        }

        /// <summary>
        /// </summary>
        /// <param name="other">
        /// </param>
        /// <returns>
        /// </returns>
        public int CompareTo(Version other)
        {
            if (other == null)
            {
                return 1;
            }

            if (this.Major != other.Major)
            {
                return this.Major.CompareTo(other.Major);
            }
            if (this.Minor != other.Minor)
            {
                return this.Minor.CompareTo(other.Minor);
            }
            if (this.Build != other.Build)
            {
                return this.Build.CompareTo(other.Build);
            }
            if (this.Revision != other.Revision)
            {
                return this.Revision.CompareTo(other.Revision);
            }
            if (this.Suffix == null)
            {
                if (other.Suffix == null)
                {
                    return 0;
                }
                return -1;
            }
            else
            {
                if (other.Suffix == null)
                {
                    return 1;
                }
                return this.Suffix.CompareTo(other.Suffix);
            }
        }

        /// <summary>
        /// </summary>
        /// <param name="obj">
        /// </param>
        /// <returns>
        /// </returns>
        public override bool Equals(object obj)
        {
            if (ReferenceEquals(obj, null) ||
                this.GetType() != obj.GetType())
            {
                return false;
            }

            return this == (Version)obj;
        }

        /// <summary>
        /// </summary>
        /// <returns>
        /// </returns>
        public override int GetHashCode()
        {
            return this.ToString().ToLower().GetHashCode();
        }

        public XmlSchema GetSchema()
        {
            return null;
        }

        public void ReadXml(XmlReader reader)
        {
            Version version = Parse(reader.ReadString());
            this.Major = version.Major;
            this.Minor = version.Minor;
            this.Build = version.Build;
            this.Revision = version.Revision;
            this.Suffix = version.Suffix;
            reader.Read();
        }

        /// <summary>
        /// </summary>
        /// <returns>
        /// </returns>
        public override string ToString()
        {
            return string.Format(
                "{0}.{1}.{2}{3}{4}",
                this.Major,
                this.Minor,
                this.Build,
                this.Revision != 0
                    ? "." + this.Revision
                    : "",
                this.Suffix);
        }

        public void WriteXml(XmlWriter writer)
        {
            writer.WriteString(this.ToString());
        }
    }
}
